import StackTrace from "stacktrace-js"
/**
 * Base class for ImageMagick input and output files.
 */
export interface MagickFile {
  name: string
}

/**
 * Represents output files generated when an ImageMagick command executes.
 */
export interface MagickOutputFile extends MagickFile {
  blob: Blob
  buffer?: ArrayBuffer
}

/**
 * Represents input files that need to be provided to {@link call} or [execute](https://github.com/KnicKnic/WASM-ImageMagick/tree/master/apidocs#execute).
 *
 * Can be builded using {@link buildInputFile}
 */
export interface MagickInputFile extends MagickFile {
  content: Uint8Array
}

/**
 * {@link call} shortcut that only returns the output files.
 */
export async function Call(inputFiles: MagickInputFile[], command: string[]): Promise<MagickOutputFile[]> {
  const result = await call(inputFiles, command)  
  for(let outputFile of result.outputFiles)
  {
    outputFile.blob = new Blob([outputFile.buffer])
  }
  return result.outputFiles
}

/**
 * The result of calling {@link call}. Also the base class of results of calling [execute](https://github.com/KnicKnic/WASM-ImageMagick/tree/master/apidocs#execute).
 */
export interface CallResult {
  /**
   * Output files generated by the command, if any
   */
  outputFiles: MagickOutputFile[]
  /**
   * Output printed by the command to stdout. For example the command `identify rose:` will print useful information to stdout
   */
  stdout: string[]
  /**
   * Output printed by the command to stderr. If `exitCode != 0` then this property could have some information about the error.
   */
  stderr: string[]
  /**
   * Exit code of the command executed. If 0 the command executed successfully, otherwise an error occurred and `stderr` could have some information about what was wrong
   */
  exitCode: number
}

/**
 * Low level execution function. All the other functions like [execute](https://github.com/KnicKnic/WASM-ImageMagick/tree/master/apidocs#execute)
 * ends up calling this one. It accept only one command and only in the form of array of strings.
 */
export function call(inputFiles: MagickInputFile[], command: string[]): Promise<CallResult> {
  const request = {
    files: inputFiles,
    args: command,
    requestNumber: magickWorkerPromisesKey,
    // transferable: true,
  }
  // let transfer = [];
  // for (let file of request.files) {
  //   if(file.content instanceof ArrayBuffer)
  //   {
  //     transfer.push(file.content)
  //   }
  //   else{
  //     transfer.push(file.content.buffer)
  //   }
  // }

  const promise = CreatePromiseEvent()
  magickWorkerPromises[magickWorkerPromisesKey] = promise
  magickWorker.postMessage(request)//,transfer)
  magickWorkerPromisesKey++
  return promise as Promise<CallResult>
}

export function CreatePromiseEvent() {
  let resolver
  let rejecter
  const emptyPromise = new Promise((resolve, reject) => {
    resolver = resolve
    rejecter = reject
  }) as Promise<{}> & { resolve?: any, reject?: any }
  emptyPromise.resolve = resolver
  emptyPromise.reject = rejecter
  return emptyPromise
}


function ChangeUrl(url, fileName)
{
    let splitUrl = url.split('/')
    splitUrl[splitUrl.length -1] = fileName
    return splitUrl.join('/')
}
function GetCurrentUrlDifferentFilename(currentUrl, fileName) {
  return ChangeUrl(currentUrl, fileName);
}
let currentJavascriptURL = './magickApi.js';

// // instead of doing the sane code of being able to just use import.meta.url 
// // (Edge doesn't work) (safari mobile, chrome, opera, firefox all do)
// // 
// // I will use stacktrace-js library to get the current file name
// //
// try {
//   // @ts-ignore
//   let packageUrl = import.meta.url;
//   currentJavascriptURL = packageUrl;
// } catch (error) {
//   // eat
// }

function GenerateStackAndGetPathAtDepth(depth){
  try {
    let stacktrace$$1 = StackTrace.getSync();
    let filePath = stacktrace$$1[depth].fileName;
    
    // if the stack trace code doesn't return a path separator
    if(filePath !== undefined && filePath.indexOf('/') === -1 && filePath.indexOf('\\') === -1){
      return undefined
    }
    return filePath

  } catch (error) {
    return undefined
  }
}

function GetCurrentFileURLHelper3() {
  // 3rd call site didn't work, so I made this complicated maze of helpers.. 

  // Pulling the filename from the 3rd call site of the stacktrace to get the full path
  // to the module. The first index is inconsistent across browsers and does not return 
  // the full path in Safari and results in the worker failing to resolve. 

  // I am preferring to do depth 0 first, as that will ensure people that do minification still works

  let filePath = GenerateStackAndGetPathAtDepth(0)
  if(filePath === undefined){
    filePath = GenerateStackAndGetPathAtDepth(2)
  }

  // if the stack trace code messes up 
  if(filePath === undefined){
    filePath = './magickApi.js';
  }

  return filePath
}

function GetCurrentFileURLHelper2() {
  return GetCurrentFileURLHelper3();
}
function GetCurrentFileURLHelper1() {
  return GetCurrentFileURLHelper2();
}
function GetCurrentFileURL() {
  return GetCurrentFileURLHelper1();
}

currentJavascriptURL = GetCurrentFileURL();

const magickWorkerUrl = GetCurrentUrlDifferentFilename(currentJavascriptURL, 'magick.js');

function GenerateMagickWorkerText(magickUrl){
  // generates code for the following
  // var magickJsCurrentPath = 'magickUrl';
  // importScripts(magickJsCurrentPath);

  return "var magickJsCurrentPath = '" + magickUrl +"';\n" +
         'importScripts(magickJsCurrentPath);'
}
let magickWorker;
if(currentJavascriptURL.startsWith('http'))
{
  // if worker is in a different domain fetch it, and run it
    magickWorker = new Worker(window.URL.createObjectURL(new Blob([GenerateMagickWorkerText(magickWorkerUrl)])));
}
else{
    magickWorker = new Worker(magickWorkerUrl);
}

const magickWorkerPromises = {}
let magickWorkerPromisesKey = 1

// handle responses as they stream in after being outputFiles by image magick
magickWorker.onmessage = e => {
  const response = e.data
  const promise = magickWorkerPromises[response.requestNumber]
  delete magickWorkerPromises[response.requestNumber]
  const result = {
    outputFiles: response.outputFiles,
    stdout: response.stdout,
    stderr: response.stderr,
    exitCode: response.exitCode || 0,
  }
  promise.resolve(result)
}
